def get_full_id(atm):
    """ncat returns atomic contacts, translates them to atomic full id tuple, should
    comply with PDBParser
    """
    # we try here to mimimc zenPDBs full id.
    (model, chain_id, res_id, res_name, res_ic, at_name, at_elem, alt_loc) = atm
    model = int(model) - 1 # 0 conting
    if not chain_id:
        chain_id = " "      # re does not catch spaces
    if not alt_loc:
        alt_loc = " "       # re does not catch spaces

    res_id = int(res_id)    # can also be negative
    at_id = at_name.strip() #
    # we loose info about atom elements at_elem
    # could fail for CAlcium atoms in residues with CA atoms
    # but zenPDB will fail anyway if the is a C alpha (discard the calcium)

    # this will attach H_ to nucleotides... could be convenient
    # could introduce D for DNA P for PROTEIN and H for Hetero
    if res_name not in AA_NAMES and res_name not in NA_NAMES:
        res_name = '%s_%s' % ('H', res_name)
    # mimic id's
    at_long_id = (at_id, alt_loc)
    res_long_id = (res_name, res_id, res_ic)
    full_id = (model, chain_id, res_long_id, at_long_id)
    # we could autput all in a dict but the definite
    # outpu are atom-atom contacts
    return full_id

def call_ncont(pdb_file, source, target, mindist = "", maxdist = "", seqdist = "", cells = "", symmetric =False):
    """Calls ncat to get desired crystal contacts
    """
    # As of now 30.01.08 the mmdb library on which ncat relies does not support PDB v3 files.
    # The problem is related to hydrogen atom names like HD21, HD22 in Asn which get both
    # translated to HD1 giving an ambiguos contact result (previously 1HD2). The program
    # does not give an error message. The solution is to use the remediator script from rcsb.
    # symmetric is by default false
    # if source == target:
    #    symmetric = True # we help NCONT but only if we do not care about the symmop

    # other options need not be changeable, accepts defaults if none given
    src_ = "source %s" % source
    tgt_ = "target %s" % target
    mnd_ = "mindist %s" % mindist
    mxd_ = "maxdist %s" % maxdist
    sqd_ = "seqdist %s" % seqdist
    cls_ = "cells '%s'" % cells
    # check if got option, and appends \n
    options = [opt + '\n' for opt in (src_, tgt_, mnd_, mxd_, sqd_, cls_) if len(split(opt)) is not 1]
    print 'Running NCONT for %s with options:\n' % (pdb_file)
    for option in options:
        print option,

    # calling ncat
    process = Popen('ncont xyzin %s' % pdb_file, shell=True, stdin=PIPE, stdout=PIPE)
    try:
        process.stdin.writelines(options)
    except IOError:
        raise PDBExternalException, 'NCONT failed to take all input'
    process.stdin.close()   #computation starts

    result = {}
    for line in process.stdout.xreadlines():    # process output sequentially, two atoms
        if re_ncont_error.search(line):         # NCONT did not calculate anything
            raise PDBExternalException, line
        if not re_slash.match(line):            # in a line mark a new id.
            continue # not a result
        #stort processing a line:
        distance = float(re_distance.search(line).group())
        symmetry = re_symmetry.search(line).group(1)
        atoms = re_id.findall(line)[::-1]
        atom_id_1 = get_full_id(atoms.pop())
        if not result.get(atom_id_1): # we didn't see this atom before
            result[atom_id_1] = []    # initiation
        if atoms:                                   # two atoms in line
            main_id = atom_id_1                     # reference atom
            atom_id_1 = get_full_id(atoms.pop())    # other atom
            if not result.get(atom_id_1):   # we didn't see this atom before
                result[atom_id_1] = []      # initiation
        # only one atom in line attach to previous main
        try:
            result[main_id].append((atom_id_1, distance, symmetry))
        except NameError:
            # sometimes NCONT get's broken and does not output
            # the full result, it's scary since a broken result
            # can sometimes be parsed correctly, how to check for
            # incomplete results?
            raise PDBExternalException, 'NCONT failed, try lowering the maxdist or chaingig "inter" > "symm"'
        if symmetric:
            # This option is disabled by default since it
            # makes no sense if source and target are different and it looses symmetry operators.
            result[atom_id_1].append((main_id, distance, symmetry)) # contacts are symmetrical?
    return result

